/*Hercules InColor emulation*/

#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "timer.h"
#include "video.h"
#include "vid_incolor.h"

/* extended CRTC registers */

#define INCOLOR_CRTC_XMODE 20   /* xMode register */
#define INCOLOR_CRTC_UNDER 21   /* Underline */
#define INCOLOR_CRTC_OVER 22    /* Overstrike */
#define INCOLOR_CRTC_EXCEPT 23  /* Exception */
#define INCOLOR_CRTC_MASK 24    /* Plane display mask & write mask */
#define INCOLOR_CRTC_RWCTRL 25  /* Read/write control */
#define INCOLOR_CRTC_RWCOL 26   /* Read/write colour */
#define INCOLOR_CRTC_PROTECT 27 /* Latch protect */
#define INCOLOR_CRTC_PALETTE 28 /* Palette */

/* character width */
#define INCOLOR_CW ((incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) ? 8 : 9)

/* mode control register */
#define INCOLOR_CTRL_GRAPH 0x02
#define INCOLOR_CTRL_ENABLE 0x08
#define INCOLOR_CTRL_BLINK 0x20
#define INCOLOR_CTRL_PAGE1 0x80

/* CRTC status register */
#define INCOLOR_STATUS_HSYNC 0x01 /* horizontal sync */
#define INCOLOR_STATUS_LIGHT 0x02
#define INCOLOR_STATUS_VIDEO 0x08
#define INCOLOR_STATUS_ID 0x50    /* Card identification */
#define INCOLOR_STATUS_VSYNC 0x80 /* -vertical sync */

/* configuration switch register */
#define INCOLOR_CTRL2_GRAPH 0x01
#define INCOLOR_CTRL2_PAGE1 0x02

/* extended mode register */
#define INCOLOR_XMODE_RAMFONT 0x01
#define INCOLOR_XMODE_90COL 0x02

/* Read/write control */
#define INCOLOR_RWCTRL_WRMODE 0x30
#define INCOLOR_RWCTRL_POLARITY 0x40

/* exception register */
#define INCOLOR_EXCEPT_CURSOR 0x0F  /* Cursor colour */
#define INCOLOR_EXCEPT_PALETTE 0x10 /* Enable palette register */
#define INCOLOR_EXCEPT_ALTATTR 0x20 /* Use alternate attributes */

/* Default palette */
static unsigned char defpal[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                                   0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F};

static uint32_t incolor_rgb[64];

/* Mapping of inks to RGB */
static unsigned char init_rgb[64][3] = {
        // rgbRGB
        {0x00, 0x00, 0x00}, // 000000
        {0x00, 0x00, 0xaa}, // 000001
        {0x00, 0xaa, 0x00}, // 000010
        {0x00, 0xaa, 0xaa}, // 000011
        {0xaa, 0x00, 0x00}, // 000100
        {0xaa, 0x00, 0xaa}, // 000101
        {0xaa, 0xaa, 0x00}, // 000110
        {0xaa, 0xaa, 0xaa}, // 000111
        {0x00, 0x00, 0x55}, // 001000
        {0x00, 0x00, 0xff}, // 001001
        {0x00, 0xaa, 0x55}, // 001010
        {0x00, 0xaa, 0xff}, // 001011
        {0xaa, 0x00, 0x55}, // 001100
        {0xaa, 0x00, 0xff}, // 001101
        {0xaa, 0xaa, 0x55}, // 001110
        {0xaa, 0xaa, 0xff}, // 001111
        {0x00, 0x55, 0x00}, // 010000
        {0x00, 0x55, 0xaa}, // 010001
        {0x00, 0xff, 0x00}, // 010010
        {0x00, 0xff, 0xaa}, // 010011
        {0xaa, 0x55, 0x00}, // 010100
        {0xaa, 0x55, 0xaa}, // 010101
        {0xaa, 0xff, 0x00}, // 010110
        {0xaa, 0xff, 0xaa}, // 010111
        {0x00, 0x55, 0x55}, // 011000
        {0x00, 0x55, 0xff}, // 011001
        {0x00, 0xff, 0x55}, // 011010
        {0x00, 0xff, 0xff}, // 011011
        {0xaa, 0x55, 0x55}, // 011100
        {0xaa, 0x55, 0xff}, // 011101
        {0xaa, 0xff, 0x55}, // 011110
        {0xaa, 0xff, 0xff}, // 011111
        {0x55, 0x00, 0x00}, // 100000
        {0x55, 0x00, 0xaa}, // 100001
        {0x55, 0xaa, 0x00}, // 100010
        {0x55, 0xaa, 0xaa}, // 100011
        {0xff, 0x00, 0x00}, // 100100
        {0xff, 0x00, 0xaa}, // 100101
        {0xff, 0xaa, 0x00}, // 100110
        {0xff, 0xaa, 0xaa}, // 100111
        {0x55, 0x00, 0x55}, // 101000
        {0x55, 0x00, 0xff}, // 101001
        {0x55, 0xaa, 0x55}, // 101010
        {0x55, 0xaa, 0xff}, // 101011
        {0xff, 0x00, 0x55}, // 101100
        {0xff, 0x00, 0xff}, // 101101
        {0xff, 0xaa, 0x55}, // 101110
        {0xff, 0xaa, 0xff}, // 101111
        {0x55, 0x55, 0x00}, // 110000
        {0x55, 0x55, 0xaa}, // 110001
        {0x55, 0xff, 0x00}, // 110010
        {0x55, 0xff, 0xaa}, // 110011
        {0xff, 0x55, 0x00}, // 110100
        {0xff, 0x55, 0xaa}, // 110101
        {0xff, 0xff, 0x00}, // 110110
        {0xff, 0xff, 0xaa}, // 110111
        {0x55, 0x55, 0x55}, // 111000
        {0x55, 0x55, 0xff}, // 111001
        {0x55, 0xff, 0x55}, // 111010
        {0x55, 0xff, 0xff}, // 111011
        {0xff, 0x55, 0x55}, // 111100
        {0xff, 0x55, 0xff}, // 111101
        {0xff, 0xff, 0x55}, // 111110
        {0xff, 0xff, 0xff}, // 111111
};

typedef struct incolor_t {
        mem_mapping_t mapping;

        uint8_t crtc[32];
        int crtcreg;

        uint8_t ctrl, ctrl2, stat;

        uint64_t dispontime, dispofftime;
        pc_timer_t timer;

        int firstline, lastline;

        int linepos, displine;
        int vc, sc;
        uint16_t ma, maback;
        int con, coff, cursoron;
        int dispon, blink;
        int vsynctime, vadj;

        uint8_t palette[16]; /* EGA-style 16 -> 64 palette registers */
        uint8_t palette_idx; /* Palette write index */
        uint8_t latch[4];    /* Memory read/write latches */
        uint8_t *vram;
} incolor_t;

void incolor_recalctimings(incolor_t *incolor);
void incolor_write(uint32_t addr, uint8_t val, void *p);
uint8_t incolor_read(uint32_t addr, void *p);

void incolor_out(uint16_t addr, uint8_t val, void *p) {
        incolor_t *incolor = (incolor_t *)p;
        /*        pclog("InColor out %04X %02X\n",addr,val); */
        switch (addr) {
        case 0x3b0:
        case 0x3b2:
        case 0x3b4:
        case 0x3b6:
                incolor->crtcreg = val & 31;
                return;
        case 0x3b1:
        case 0x3b3:
        case 0x3b5:
        case 0x3b7:
                if (incolor->crtcreg > 28)
                        return;
                /* Palette load register */
                if (incolor->crtcreg == INCOLOR_CRTC_PALETTE) {
                        incolor->palette[incolor->palette_idx % 16] = val;
                        ++incolor->palette_idx;
                }
                incolor->crtc[incolor->crtcreg] = val;
                if (incolor->crtc[10] == 6 &&
                    incolor->crtc[11] == 7) /*Fix for Generic Turbo XT BIOS, which sets up cursor registers wrong*/
                {
                        incolor->crtc[10] = 0xb;
                        incolor->crtc[11] = 0xc;
                }
                incolor_recalctimings(incolor);
                return;
        case 0x3b8:
                incolor->ctrl = val;
                return;
        case 0x3bf:
                incolor->ctrl2 = val;
                if (val & 2)
                        mem_mapping_set_addr(&incolor->mapping, 0xb0000, 0x10000);
                else
                        mem_mapping_set_addr(&incolor->mapping, 0xb0000, 0x08000);
                return;
        }
}

uint8_t incolor_in(uint16_t addr, void *p) {
        incolor_t *incolor = (incolor_t *)p;
        /*        pclog("InColor in %04X %02X %04X:%04X %04X\n",addr,(incolor->stat & 0xF) | ((incolor->stat & 8) << 4),CS,pc,CX);
         */
        switch (addr) {
        case 0x3b0:
        case 0x3b2:
        case 0x3b4:
        case 0x3b6:
                return incolor->crtcreg;
        case 0x3b1:
        case 0x3b3:
        case 0x3b5:
        case 0x3b7:
                if (incolor->crtcreg > 28)
                        return 0xff;
                incolor->palette_idx = 0; /* Read resets the palette index */
                return incolor->crtc[incolor->crtcreg];
        case 0x3ba:
                /* 0x50: InColor card identity */
                return (incolor->stat & 0xf) | ((incolor->stat & 8) << 4) | 0x50;
        }
        return 0xff;
}

void incolor_write(uint32_t addr, uint8_t val, void *p) {
        incolor_t *incolor = (incolor_t *)p;
        egawrites++;

        int plane;
        unsigned char wmask = incolor->crtc[INCOLOR_CRTC_MASK];
        unsigned char wmode = incolor->crtc[INCOLOR_CRTC_RWCTRL] & INCOLOR_RWCTRL_WRMODE;
        unsigned char fg = incolor->crtc[INCOLOR_CRTC_RWCOL] & 0x0F;
        unsigned char bg = (incolor->crtc[INCOLOR_CRTC_RWCOL] >> 4) & 0x0F;
        unsigned char w = 0;
        unsigned char vmask; /* Mask of bit within byte */
        unsigned char pmask; /* Mask of plane within colour value */
        unsigned char latch;

        addr &= 0xFFFF;

        /* In text mode, writes to the bottom 16k always touch all 4 planes */
        if (!(incolor->ctrl & INCOLOR_CTRL_GRAPH) && addr < 0x4000) {
                incolor->vram[addr] = val;
                return;
        }

        /* There are four write modes:
         * 0: 1 => foreground,    0 => background
         * 1: 1 => foreground,    0 => source latch
         * 2: 1 => source latch,  0 => background
         * 3: 1 => source latch,  0 => ~source latch
         */
        pmask = 1;
        for (plane = 0; plane < 4; pmask <<= 1, wmask >>= 1, addr += 0x10000, plane++) {
                if (wmask & 0x10) /* Ignore writes to selected plane */
                {
                        continue;
                }
                latch = incolor->latch[plane];
                for (vmask = 0x80; vmask != 0; vmask >>= 1) {
                        switch (wmode) {
                        case 0x00:
                                if (val & vmask)
                                        w = (fg & pmask);
                                else
                                        w = (bg & pmask);
                                break;
                        case 0x10:
                                if (val & vmask)
                                        w = (fg & pmask);
                                else
                                        w = (latch & vmask);
                                break;
                        case 0x20:
                                if (val & vmask)
                                        w = (latch & vmask);
                                else
                                        w = (bg & pmask);
                                break;
                        case 0x30:
                                if (val & vmask)
                                        w = (latch & vmask);
                                else
                                        w = ((~latch) & vmask);
                                break;
                        }
                        /* w is nonzero to write a 1, zero to write a 0 */
                        if (w)
                                incolor->vram[addr] |= vmask;
                        else
                                incolor->vram[addr] &= ~vmask;
                }
        }
}

uint8_t incolor_read(uint32_t addr, void *p) {
        incolor_t *incolor = (incolor_t *)p;
        egareads++;
        unsigned plane;
        unsigned char lp = incolor->crtc[INCOLOR_CRTC_PROTECT];
        unsigned char value = 0;
        unsigned char dc; /* "don't care" register */
        unsigned char bg; /* background colour */
        unsigned char fg;
        unsigned char mask, pmask;

        addr &= 0xFFFF;
        /* Read the four planes into latches */
        for (plane = 0; plane < 4; plane++, addr += 0x10000) {
                incolor->latch[plane] &= lp;
                incolor->latch[plane] |= (incolor->vram[addr] & ~lp);
        }
        addr &= 0xFFFF;
        /* In text mode, reads from the bottom 16k assume all planes have
         * the same contents */
        if (!(incolor->ctrl & INCOLOR_CTRL_GRAPH) && addr < 0x4000) {
                return incolor->latch[0];
        }
        /* For each pixel, work out if its colour matches the background */
        for (mask = 0x80; mask != 0; mask >>= 1) {
                fg = 0;
                dc = incolor->crtc[INCOLOR_CRTC_RWCTRL] & 0x0F;
                bg = (incolor->crtc[INCOLOR_CRTC_RWCOL] >> 4) & 0x0F;
                for (plane = 0, pmask = 1; plane < 4; plane++, pmask <<= 1) {
                        if (dc & pmask) {
                                fg |= (bg & pmask);
                        } else if (incolor->latch[plane] & mask) {
                                fg |= pmask;
                        }
                }
                if (bg == fg)
                        value |= mask;
        }
        if (incolor->crtc[INCOLOR_CRTC_RWCTRL] & INCOLOR_RWCTRL_POLARITY) {
                value = ~value;
        }
        return value;
}

void incolor_recalctimings(incolor_t *incolor) {
        double disptime;
        double _dispontime, _dispofftime;
        disptime = incolor->crtc[0] + 1;
        _dispontime = incolor->crtc[1];
        _dispofftime = disptime - _dispontime;
        _dispontime *= MDACONST;
        _dispofftime *= MDACONST;
        incolor->dispontime = (uint64_t)_dispontime;
        incolor->dispofftime = (uint64_t)_dispofftime;
}

static void incolor_draw_char_rom(incolor_t *incolor, int x, uint8_t chr, uint8_t attr) {
        unsigned i;
        int elg, blk;
        unsigned ull;
        unsigned val;
        unsigned ifg, ibg;
        const unsigned char *fnt;
        uint32_t fg, bg;
        int cw = INCOLOR_CW;

        blk = 0;
        if (incolor->ctrl & INCOLOR_CTRL_BLINK) {
                if (attr & 0x80) {
                        blk = (incolor->blink & 16);
                }
                attr &= 0x7f;
        }

        if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR) {
                /* MDA-compatible attributes */
                ibg = 0;
                ifg = 7;
                if ((attr & 0x77) == 0x70) /* Invert */
                {
                        ifg = 0;
                        ibg = 7;
                }
                if (attr & 8) {
                        ifg |= 8; /* High intensity FG */
                }
                if (attr & 0x80) {
                        ibg |= 8; /* High intensity BG */
                }
                if ((attr & 0x77) == 0) /* Blank */
                {
                        ifg = ibg;
                }
                ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
        } else {
                /* CGA-compatible attributes */
                ull = 0xffff;
                ifg = attr & 0x0F;
                ibg = (attr >> 4) & 0x0F;
        }
        if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE) {
                fg = incolor_rgb[incolor->palette[ifg]];
                bg = incolor_rgb[incolor->palette[ibg]];
        } else {
                fg = incolor_rgb[defpal[ifg]];
                bg = incolor_rgb[defpal[ibg]];
        }

        /* ELG set to stretch 8px character to 9px */
        elg = ((chr >= 0xc0) && (chr <= 0xdf));

        fnt = &(fontdatm[chr][incolor->sc]);

        if (blk) {
                val = 0x000; /* Blinking, draw all background */
        } else if (incolor->sc == ull) {
                val = 0x1ff; /* Underscore, draw all foreground */
        } else {
                val = fnt[0] << 1;

                if (elg) {
                        val |= (val >> 1) & 1;
                }
        }
        for (i = 0; i < cw; i++) {
                ((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = (val & 0x100) ? fg : bg;
                val = val << 1;
        }
}

static void incolor_draw_char_ram4(incolor_t *incolor, int x, uint8_t chr, uint8_t attr) {
        unsigned i;
        int elg, blk;
        unsigned ull;
        unsigned val[4];
        unsigned ifg, ibg, cfg, pmask, plane;
        const unsigned char *fnt;
        uint32_t fg;
        int cw = INCOLOR_CW;
        int blink = incolor->ctrl & INCOLOR_CTRL_BLINK;
        int altattr = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR;
        int palette = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE;

        blk = 0;
        if (blink) {
                if (attr & 0x80) {
                        blk = (incolor->blink & 16);
                }
                attr &= 0x7f;
        }

        if (altattr) {
                /* MDA-compatible attributes */
                ibg = 0;
                ifg = 7;
                if ((attr & 0x77) == 0x70) /* Invert */
                {
                        ifg = 0;
                        ibg = 7;
                }
                if (attr & 8) {
                        ifg |= 8; /* High intensity FG */
                }
                if (attr & 0x80) {
                        ibg |= 8; /* High intensity BG */
                }
                if ((attr & 0x77) == 0) /* Blank */
                {
                        ifg = ibg;
                }
                ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
        } else {
                /* CGA-compatible attributes */
                ull = 0xffff;
                ifg = attr & 0x0F;
                ibg = (attr >> 4) & 0x0F;
        }
        if (incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) {
                elg = 0;
        } else {
                elg = ((chr >= 0xc0) && (chr <= 0xdf));
        }
        fnt = incolor->vram + 0x4000 + 16 * chr + incolor->sc;

        if (blk) {
                /* Blinking, draw all background */
                val[0] = val[1] = val[2] = val[3] = 0x000;
        } else if (incolor->sc == ull) {
                /* Underscore, draw all foreground */
                val[0] = val[1] = val[2] = val[3] = 0x1ff;
        } else {
                val[0] = fnt[0x00000] << 1;
                val[1] = fnt[0x10000] << 1;
                val[2] = fnt[0x20000] << 1;
                val[3] = fnt[0x30000] << 1;

                if (elg) {
                        val[0] |= (val[0] >> 1) & 1;
                        val[1] |= (val[1] >> 1) & 1;
                        val[2] |= (val[2] >> 1) & 1;
                        val[3] |= (val[3] >> 1) & 1;
                }
        }
        for (i = 0; i < cw; i++) {
                /* Generate pixel colour */
                cfg = 0;
                pmask = 1;
                for (plane = 0; plane < 4; plane++, pmask = pmask << 1) {
                        if (val[plane] & 0x100)
                                cfg |= (ifg & pmask);
                        else
                                cfg |= (ibg & pmask);
                }
                /* cfg = colour of foreground pixels */
                if (altattr && (attr & 0x77) == 0)
                        cfg = ibg; /* 'blank' attribute */
                if (palette) {
                        fg = incolor_rgb[incolor->palette[cfg]];
                } else {
                        fg = incolor_rgb[defpal[cfg]];
                }

                ((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = fg;
                val[0] = val[0] << 1;
                val[1] = val[1] << 1;
                val[2] = val[2] << 1;
                val[3] = val[3] << 1;
        }
}

static void incolor_draw_char_ram48(incolor_t *incolor, int x, uint8_t chr, uint8_t attr) {
        unsigned i;
        int elg, blk, ul, ol, bld;
        unsigned ull, oll, ulc = 0, olc = 0;
        unsigned val[4];
        unsigned ifg = 0, ibg, cfg, pmask, plane;
        const unsigned char *fnt;
        uint32_t fg;
        int cw = INCOLOR_CW;
        int blink = incolor->ctrl & INCOLOR_CTRL_BLINK;
        int altattr = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR;
        int palette = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE;
        int font = (attr & 0x0F);

        if (font >= 12)
                font &= 7;

        blk = 0;
        if (blink && altattr) {
                if (attr & 0x40) {
                        blk = (incolor->blink & 16);
                }
                attr &= 0x7f;
        }
        if (altattr) {
                /* MDA-compatible attributes */
                if (blink) {
                        ibg = (attr & 0x80) ? 8 : 0;
                        bld = 0;
                        ol = (attr & 0x20) ? 1 : 0;
                        ul = (attr & 0x10) ? 1 : 0;
                } else {
                        bld = (attr & 0x80) ? 1 : 0;
                        ibg = (attr & 0x40) ? 0x0F : 0;
                        ol = (attr & 0x20) ? 1 : 0;
                        ul = (attr & 0x10) ? 1 : 0;
                }
        } else {
                /* CGA-compatible attributes */
                ibg = 0;
                ifg = (attr >> 4) & 0x0F;
                ol = 0;
                ul = 0;
                bld = 0;
        }
        if (ul) {
                ull = incolor->crtc[INCOLOR_CRTC_UNDER] & 0x0F;
                ulc = (incolor->crtc[INCOLOR_CRTC_UNDER] >> 4) & 0x0F;
                if (ulc == 0)
                        ulc = 7;
        } else {
                ull = 0xFFFF;
        }
        if (ol) {
                oll = incolor->crtc[INCOLOR_CRTC_OVER] & 0x0F;
                olc = (incolor->crtc[INCOLOR_CRTC_OVER] >> 4) & 0x0F;
                if (olc == 0)
                        olc = 7;
        } else {
                oll = 0xFFFF;
        }

        if (incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) {
                elg = 0;
        } else {
                elg = ((chr >= 0xc0) && (chr <= 0xdf));
        }
        fnt = incolor->vram + 0x4000 + 16 * chr + 4096 * font + incolor->sc;

        if (blk) {
                /* Blinking, draw all background */
                val[0] = val[1] = val[2] = val[3] = 0x000;
        } else if (incolor->sc == ull) {
                /* Underscore, draw all foreground */
                val[0] = val[1] = val[2] = val[3] = 0x1ff;
        } else {
                val[0] = fnt[0x00000] << 1;
                val[1] = fnt[0x10000] << 1;
                val[2] = fnt[0x20000] << 1;
                val[3] = fnt[0x30000] << 1;

                if (elg) {
                        val[0] |= (val[0] >> 1) & 1;
                        val[1] |= (val[1] >> 1) & 1;
                        val[2] |= (val[2] >> 1) & 1;
                        val[3] |= (val[3] >> 1) & 1;
                }
                if (bld) {
                        val[0] |= (val[0] >> 1);
                        val[1] |= (val[1] >> 1);
                        val[2] |= (val[2] >> 1);
                        val[3] |= (val[3] >> 1);
                }
        }
        for (i = 0; i < cw; i++) {
                /* Generate pixel colour */
                cfg = 0;
                pmask = 1;
                if (incolor->sc == oll) {
                        cfg = olc ^ ibg; /* Strikethrough */
                } else if (incolor->sc == ull) {
                        cfg = ulc ^ ibg; /* Underline */
                } else {
                        for (plane = 0; plane < 4; plane++, pmask = pmask << 1) {
                                if (val[plane] & 0x100) {
                                        if (altattr)
                                                cfg |= ((~ibg) & pmask);
                                        else
                                                cfg |= ((~ifg) & pmask);
                                } else if (altattr)
                                        cfg |= (ibg & pmask);
                        }
                }
                if (palette) {
                        fg = incolor_rgb[incolor->palette[cfg]];
                } else {
                        fg = incolor_rgb[defpal[cfg]];
                }

                ((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = fg;
                val[0] = val[0] << 1;
                val[1] = val[1] << 1;
                val[2] = val[2] << 1;
                val[3] = val[3] << 1;
        }
}

static void incolor_text_line(incolor_t *incolor, uint16_t ca) {
        int drawcursor;
        int x, c;
        uint8_t chr, attr;
        uint32_t col;

        for (x = 0; x < incolor->crtc[1]; x++) {
                chr = incolor->vram[(incolor->ma << 1) & 0x3fff];
                attr = incolor->vram[((incolor->ma << 1) + 1) & 0x3fff];

                drawcursor = ((incolor->ma == ca) && incolor->con && incolor->cursoron);

                switch (incolor->crtc[INCOLOR_CRTC_XMODE] & 5) {
                case 0:
                case 4: /* ROM font */
                        incolor_draw_char_rom(incolor, x, chr, attr);
                        break;
                case 1: /* 4k RAMfont */
                        incolor_draw_char_ram4(incolor, x, chr, attr);
                        break;
                case 5: /* 48k RAMfont */
                        incolor_draw_char_ram48(incolor, x, chr, attr);
                        break;
                }
                ++incolor->ma;
                if (drawcursor) {
                        int cw = INCOLOR_CW;
                        uint8_t ink = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_CURSOR;
                        if (ink == 0)
                                ink = (attr & 0x08) | 7;

                        /* In MDA-compatible mode, cursor brightness comes from
                         * background */
                        if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR) {
                                ink = (attr & 0x08) | (ink & 7);
                        }
                        if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE) {
                                col = incolor_rgb[incolor->palette[ink]];
                        } else {
                                col = incolor_rgb[defpal[ink]];
                        }
                        for (c = 0; c < cw; c++) {
                                ((uint32_t *)buffer32->line[incolor->displine])[x * cw + c] = col;
                        }
                }
        }
}

static void incolor_graphics_line(incolor_t *incolor) {
        uint8_t mask;
        uint16_t ca;
        int x, c, plane, col;
        uint8_t ink;
        uint16_t val[4];

        /* Graphics mode. */
        ca = (incolor->sc & 3) * 0x2000;
        if ((incolor->ctrl & INCOLOR_CTRL_PAGE1) && (incolor->ctrl2 & INCOLOR_CTRL2_PAGE1))
                ca += 0x8000;

        for (x = 0; x < incolor->crtc[1]; x++) {
                mask = incolor->crtc[INCOLOR_CRTC_MASK]; /* Planes to display */
                for (plane = 0; plane < 4; plane++, mask = mask >> 1) {
                        if (mask & 1)
                                val[plane] = (incolor->vram[((incolor->ma << 1) & 0x1fff) + ca + 0x10000 * plane] << 8) |
                                             incolor->vram[((incolor->ma << 1) & 0x1fff) + ca + 0x10000 * plane + 1];
                        else
                                val[plane] = 0;
                }
                incolor->ma++;
                for (c = 0; c < 16; c++) {
                        ink = 0;
                        for (plane = 0; plane < 4; plane++) {
                                ink = ink >> 1;
                                if (val[plane] & 0x8000)
                                        ink |= 8;
                                val[plane] = val[plane] << 1;
                        }
                        /* Is palette in use? */
                        if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE)
                                col = incolor->palette[ink];
                        else
                                col = defpal[ink];

                        ((uint32_t *)buffer32->line[incolor->displine])[(x << 4) + c] = incolor_rgb[col];
                }
        }
}

void incolor_poll(void *p) {
        incolor_t *incolor = (incolor_t *)p;
        uint16_t ca = (incolor->crtc[15] | (incolor->crtc[14] << 8)) & 0x3fff;
        int x;
        int oldvc;
        int oldsc;

        if (!incolor->linepos) {
                //                pclog("InColor poll %i %i\n", incolor->vc, incolor->sc);
                timer_advance_u64(&incolor->timer, incolor->dispofftime);
                incolor->stat |= 1;
                incolor->linepos = 1;
                oldsc = incolor->sc;
                if ((incolor->crtc[8] & 3) == 3)
                        incolor->sc = (incolor->sc << 1) & 7;
                if (incolor->dispon) {
                        if (incolor->displine < incolor->firstline) {
                                incolor->firstline = incolor->displine;
                                video_wait_for_buffer();
                        }
                        incolor->lastline = incolor->displine;
                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH)) {
                                incolor_graphics_line(incolor);
                        } else {
                                incolor_text_line(incolor, ca);
                        }
                }
                incolor->sc = oldsc;
                if (incolor->vc == incolor->crtc[7] && !incolor->sc) {
                        incolor->stat |= 8;
                        //                        printf("VSYNC on %i %i\n",vc,sc);
                }
                incolor->displine++;
                if (incolor->displine >= 500)
                        incolor->displine = 0;
        } else {
                timer_advance_u64(&incolor->timer, incolor->dispontime);
                if (incolor->dispon)
                        incolor->stat &= ~1;
                incolor->linepos = 0;
                if (incolor->vsynctime) {
                        incolor->vsynctime--;
                        if (!incolor->vsynctime) {
                                incolor->stat &= ~8;
                                //                                printf("VSYNC off %i %i\n",vc,sc);
                        }
                }
                if (incolor->sc == (incolor->crtc[11] & 31) ||
                    ((incolor->crtc[8] & 3) == 3 && incolor->sc == ((incolor->crtc[11] & 31) >> 1))) {
                        incolor->con = 0;
                        incolor->coff = 1;
                }
                if (incolor->vadj) {
                        incolor->sc++;
                        incolor->sc &= 31;
                        incolor->ma = incolor->maback;
                        incolor->vadj--;
                        if (!incolor->vadj) {
                                incolor->dispon = 1;
                                incolor->ma = incolor->maback = (incolor->crtc[13] | (incolor->crtc[12] << 8)) & 0x3fff;
                                incolor->sc = 0;
                        }
                } else if (incolor->sc == incolor->crtc[9] ||
                           ((incolor->crtc[8] & 3) == 3 && incolor->sc == (incolor->crtc[9] >> 1))) {
                        incolor->maback = incolor->ma;
                        incolor->sc = 0;
                        oldvc = incolor->vc;
                        incolor->vc++;
                        incolor->vc &= 127;
                        if (incolor->vc == incolor->crtc[6])
                                incolor->dispon = 0;
                        if (oldvc == incolor->crtc[4]) {
                                //                                printf("Display over at %i\n",displine);
                                incolor->vc = 0;
                                incolor->vadj = incolor->crtc[5];
                                if (!incolor->vadj)
                                        incolor->dispon = 1;
                                if (!incolor->vadj)
                                        incolor->ma = incolor->maback = (incolor->crtc[13] | (incolor->crtc[12] << 8)) & 0x3fff;
                                if ((incolor->crtc[10] & 0x60) == 0x20)
                                        incolor->cursoron = 0;
                                else
                                        incolor->cursoron = incolor->blink & 16;
                        }
                        if (incolor->vc == incolor->crtc[7]) {
                                incolor->dispon = 0;
                                incolor->displine = 0;
                                incolor->vsynctime = 16; //(crtcm[3]>>4)+1;
                                if (incolor->crtc[7]) {
                                        //                                        printf("Lastline %i Firstline %i
                                        //                                        %i\n",lastline,firstline,lastline-firstline);
                                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH)) {
                                                x = incolor->crtc[1] << 4;
                                        } else {
                                                x = incolor->crtc[1] * 9;
                                        }
                                        incolor->lastline++;
                                        if (x != xsize || (incolor->lastline - incolor->firstline) != ysize) {
                                                xsize = x;
                                                ysize = incolor->lastline - incolor->firstline;
                                                //                                                printf("Resize to %i,%i - R1
                                                //                                                %i\n",xsize,ysize,crtcm[1]);
                                                if (xsize < 64)
                                                        xsize = 656;
                                                if (ysize < 32)
                                                        ysize = 200;
                                                updatewindowsize(xsize, ysize);
                                        }

                                        video_blit_memtoscreen(0, incolor->firstline, 0, incolor->lastline - incolor->firstline,
                                                               xsize, incolor->lastline - incolor->firstline);

                                        frames++;
                                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH)) {
                                                video_res_x = incolor->crtc[1] * 16;
                                                video_res_y = incolor->crtc[6] * 4;
                                                video_bpp = 1;
                                        } else {
                                                video_res_x = incolor->crtc[1];
                                                video_res_y = incolor->crtc[6];
                                                video_bpp = 0;
                                        }
                                }
                                incolor->firstline = 1000;
                                incolor->lastline = 0;
                                incolor->blink++;
                        }
                } else {
                        incolor->sc++;
                        incolor->sc &= 31;
                        incolor->ma = incolor->maback;
                }
                if ((incolor->sc == (incolor->crtc[10] & 31) ||
                     ((incolor->crtc[8] & 3) == 3 && incolor->sc == ((incolor->crtc[10] & 31) >> 1)))) {
                        incolor->con = 1;
                        //                        printf("Cursor on - %02X %02X %02X\n",crtcm[8],crtcm[10],crtcm[11]);
                }
        }
}

void *incolor_init() {
        int c;
        incolor_t *incolor = malloc(sizeof(incolor_t));
        memset(incolor, 0, sizeof(incolor_t));

        incolor->vram = malloc(0x40000); /* 4 planes of 64k */

        timer_add(&incolor->timer, incolor_poll, incolor, 1);
        mem_mapping_add(&incolor->mapping, 0xb0000, 0x08000, incolor_read, NULL, NULL, incolor_write, NULL, NULL, NULL,
                        MEM_MAPPING_EXTERNAL, incolor);
        io_sethandler(0x03b0, 0x0010, incolor_in, NULL, NULL, incolor_out, NULL, NULL, incolor);

        for (c = 0; c < 64; c++) {
                incolor_rgb[c] = makecol32(init_rgb[c][0], init_rgb[c][1], init_rgb[c][2]);
        }

        /* Initialise CRTC regs to safe values */
        incolor->crtc[INCOLOR_CRTC_MASK] = 0x0F; /* All planes displayed */
        incolor->crtc[INCOLOR_CRTC_RWCTRL] = INCOLOR_RWCTRL_POLARITY;
        incolor->crtc[INCOLOR_CRTC_RWCOL] = 0x0F; /* White on black */
        incolor->crtc[INCOLOR_CRTC_EXCEPT] = INCOLOR_EXCEPT_ALTATTR;
        for (c = 0; c < 16; c++) {
                incolor->palette[c] = defpal[c];
        }
        incolor->palette_idx = 0;

        return incolor;
}

void incolor_close(void *p) {
        incolor_t *incolor = (incolor_t *)p;

        free(incolor->vram);
        free(incolor);
}

void incolor_speed_changed(void *p) {
        incolor_t *incolor = (incolor_t *)p;

        incolor_recalctimings(incolor);
}

device_t incolor_device = {"Hercules InColor", 0, incolor_init, incolor_close, NULL, incolor_speed_changed, NULL, NULL};
